package org.dei.perla.sys.device.fpc.DSPICLog;

import java.util.ArrayList;
import org.dei.perla.parser.clauses.OnUnsupportedSampleRateOptions;
import org.dei.perla.parser.expressions.Constant;
import org.dei.perla.parser.util.PairValue;
import org.dei.perla.sys.device.fpc.DBRecordUtil;
import org.dei.perla.sys.device.fpc.FPC;
import org.dei.perla.sys.device.fpc.DSPICLog.datastructures.DevicesLog;
import org.dei.perla.sys.llqruntime.datastructures.QueryRecordStructure;
import org.dei.perla.sys.llqruntime.datastructures.Record;
import org.dei.perla.utils.dataconverter.ArrayUtility;
import org.dei.perla.utils.dataconverter.DataConverter;
import org.dei.perla.utils.dataconverter.annotations.StructInfo;
import org.dei.perla.utils.logger.SimpleLogger;
import org.dei.perla.utils.messages.AdapterFpcMessage;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.pipe.PipePair;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

public class DSPICLog extends FPC{

	
	private Pipe<ArrayList<PairValue<String, ? extends Constant>>> pDataCollectorPipeIn;
	private ArrayList<Pipe<Record>> pDataCollectorPipeOut;
	private Pipe<AdapterFpcMessage> pFpcPipeIn;
	private Waiter<Pipe<AdapterFpcMessage>> pFpcPipeInWaiter;
	private int pDevNumber;
	private int structSize;
	private QueryRecordStructure devicesLogRecordStructure;
	private Thread pFpcListener;

	public DSPICLog(String deviceName, PipePair<AdapterFpcMessage, AdapterFpcMessage> parPipePair, int parDevNumber) {
		StructInfo structInfo;
		pDeviceName = new String(deviceName);
		pDataCollectorPipeIn = new Pipe<ArrayList<PairValue<String, ? extends Constant>>>("DSPICLog " + parDevNumber + " DataCollectorPipeIn");
		pDataCollectorPipeIn.start();
		pDataCollectorPipeOut = new ArrayList<Pipe<Record>>();
		pFpcPipeIn = parPipePair.getInPipe();
		pFpcPipeInWaiter = new Waiter<Pipe<AdapterFpcMessage>>();
		pFpcPipeInWaiter.addWaitable(pFpcPipeIn.getWaitable());
		pFpcPipeInWaiter.start();
		pDevNumber = parDevNumber;
		
		structInfo = DevicesLog.class.getAnnotation(StructInfo.class);
		structSize = structInfo.totalStructSize();
		
		// Creo la struttura del record per il campionamento continuo
		devicesLogRecordStructure  = DBRecordUtil.createDevicesLogRecordStructure();
		
		pFpcListener = new Thread(new PipeReader());
		pFpcListener.start();
	}

	
	@Override
	protected void mngNormalMsg(byte[] parPayload) {
		this.manageData(ArrayUtility.toByteClassArray(parPayload));
	}

	/**
	 * Gestisce i byte ricevuti dal mezzo fisico e li tratta come risultato di un sampling ad eventi 
	 * @param payload byte ricevuti da interpretare
	 */
	private void manageData(Byte[] payload) {
		// Converto da array di byte (blob) a struttura
		DevicesLog data = new DevicesLog();
		
		do {
			try {
				payload = DataConverter.fromByteArray(data, payload);
				Record record = DBRecordUtil.prepareDevicesLogRecord(data, devicesLogRecordStructure, pDevNumber);
				enqueueRecord(record);
			}catch(IllegalArgumentException e) {
				SimpleLogger.log(this.getClass(), "Errore durante la conversione da array di byte a struttura: " + e.getMessage());
				break;
			}
		} while(payload.length > structSize);
	}

	/**
	 * Esegue l'enqueue di un record su tutte le pipe in uscita dell'FPC
	 * @param record record da inserire nelle pipes
	 */
	private synchronized void enqueueRecord(Record record) {
		for (Pipe<Record> p : pDataCollectorPipeOut) {
			p.enqueue(record);
		}
	}
	
	@Override
	
	public Pipe<Record> registerQuery(ArrayList<String> parAttrbiutesNames) {
		Pipe<Record> tmpPipe = new Pipe<Record>("MPPT" + pDevNumber + "_" + 
				pDataCollectorPipeOut.size() + " DataCollectorPipeOut");
		tmpPipe.start();
		pDataCollectorPipeOut.add(tmpPipe);
		return tmpPipe;
	}


	@Override
	public boolean setSamplingTime(long parTime,
			OnUnsupportedSampleRateOptions parBehaviour) {
		return false;
	}

	@Override
	public void setUpdateAttributes(ArrayList<PairValue<String, ? extends Constant>> attributeToSetList) {
		return;
	}

	/**
	 * Invia un null su tutte le pipe in uscita in modo che gli
	 * oggetti in attesa di dati dalle Pipe possano terminare
	 */
	private synchronized void stopRegisteredListeners() {
		for(Pipe<?> p : pDataCollectorPipeOut) {
			p.enqueue(null);
		}
	}
	
	/**
	 * Inner class per la gestione della lettura sulla pipe di ingresso
	 * @author Marco Fortunato
	 */
	private class PipeReader implements Runnable {
		
		public void run() {
			Waitable<Pipe<AdapterFpcMessage>> waitable;
			Pipe<AdapterFpcMessage> pipe;
			AdapterFpcMessage message;
			
			while(true) {
				waitable = pFpcPipeInWaiter.waitNext();
				pipe = waitable.getParentComponent();
				message = pipe.dequeue();
				
				if (message != null) {
				 	mngNormalMsg(message.getPayload());
				} else {
					pFpcPipeInWaiter.removeWaitable(waitable);
					pipe.stop();
					stopRegisteredListeners();
					break;
				}
			}
			SimpleLogger.log(this.getClass(), "DSPICLOG_" + pDevNumber + " terminated!");
		}
	}
	
}
